import { _decorator, Component, EventTouch, find, instantiate, Node, NodePool, Prefab, Size, size, SpriteFrame, tween, Tween, UITransform, v2, v3, Vec2, Vec3, Widget } from 'cc';
import { DataProxy } from '../../../../frame/A-LIB/sources/lib.b.data';
import { AudioManager } from '../../../../frame/component/audio-manager';
import GameDC from '../../../data/data.game.center';
import GameAsset from '../../../manager/asset.manager';
import DataManager from '../../../manager/data.manager';
import { Tile } from '../../structure/tile';
import { Guide } from '../guide';
const { ccclass, property } = _decorator;

@ccclass('GameBoard')
export class GameBoard extends Component {
	private static ins: GameBoard;
	private static select_call_list: Array<(record: {tier: number, coor: Vec2, tile: Tile})=>any> = [];
	public static onSelectTile(call: (record: {tier: number, coor: Vec2, tile: Tile})=>any){
		GameBoard.select_call_list.push(call);
	}

	private static tile_pool = new NodePool();
	private static getTile(){
		let node = GameBoard.tile_pool.get() || instantiate(GameBoard.ins.tile_prefab);

		return node.getComponent(Tile);
	}

	public static recycleTile(tile: Tile){
		if(!tile) return void 0;
		GameBoard.tile_pool.put(tile.node);
		tile.node.active = true;
	}

	@property(UITransform)
	private ui_container: UITransform = null;
	@property(Prefab)
	private tile_prefab: Prefab = null;
	@property([SpriteFrame])
	private tile_sframes: SpriteFrame[] = [];

	private uitrans: UITransform;

	private vecasit: mtec.cc.VecAssist;

	/** 棋盘尺寸 */
	private size = size(8, 8);
	private tile_size: Size;
	private tween_speed = 0;

	private tile_records: Array<{
		tier: number,
		node: Node,
		tiles: Array<{coor: Vec2, tile: Tile}>
	}> = [];
	private tile_count = 0;

	private prms_render: Promise<'end'>;
	private inited_np: mtec.NudityPromise<'end'>;

	public get tileCount(){
		return this.tile_count;
	}

	protected onLoad(){
		GameBoard.ins = this;
		this.init();
	}

	protected start(){
		this.adaptBoard();

		this.vecasit.vec.x = this.vecasit.over_right;
		this.node.setPosition(this.vecasit.vec);

		let tile_width = this.uitrans.width / this.size.width;
		let tile_height = this.uitrans.height / this.size.height;
		this.tile_size = Tile.calculateBoxSize(tile_width, tile_height).box;
		this.tween_speed = Math.max(this.tile_size.width, this.tile_size.height) / 0.1;

		this.inited_np.resolve('end');
	}

	private update_call_list: Array<[Function, any[], mtec.NudityPromise<any>]> = [];
	protected update(dt: number){
		if(this.update_call_list.length>0){
			let [call, args, np] = this.update_call_list.pop();
			np.resolve(call(...args));
		}
	}

	protected onDestroy(){
		this.listening_list.forEach(reg=>DataProxy.cancel(reg));
	}

	private adaptBoard(){
		let wgt = this.node.getComponent(Widget);
		if(!wgt){
			wgt = this.node.addComponent(Widget);
		}
		wgt.enabled = true;

		[wgt.isAlignTop, wgt.isAlignBottom, wgt.isAlignLeft, wgt.isAlignRight] = [true, true, true, true];
		[wgt.isAbsoluteTop, wgt.isAbsoluteBottom, wgt.isAbsoluteLeft, wgt.isAbsoluteRight] = [true, true, true, true];
		wgt.top = mtec.cc.is_long_screen ? 480 : 380;
		wgt.bottom = 560;
		wgt.left = 0; wgt.right = 0;

		wgt.updateAlignment();
		wgt.enabled = false;
		wgt.destroy();

		this.ui_container.setContentSize(this.uitrans.contentSize);
		this.ui_container.node.setPosition(Vec3.ZERO.clone());
	}

	private updateCallBak<A extends any[], R>(call: (...args: [...A])=>R, ...args: A){
		let np = new mtec.NudityPromise<R>();
		this.update_call_list.unshift([call, args, np]);
		return np.promise;
	}

	private updateFor<A extends any[], R>(list: A, call: (el: A[number], index: number, arr: A)=>R){
		return Promise.all(list.map((el, index, arr)=>this.updateCallBak(call, el, index, arr)));
	}

	private init(){
		this.inited_np = new mtec.NudityPromise();
		Tile.initIconSpriteFrame(this.tile_sframes);
		this.tile_sframes = null;
		this.uitrans = this.node.getComponent(UITransform);
		this.vecasit = new mtec.cc.VecAssist(this.node);

		this.listening();

		this.node.on(Node.EventType.TOUCH_END, this.touchEnd, this);
	}

	private listening_list: ReturnType<typeof DataProxy.follow>[] = [];
	private listening(){
		this.listening_list.push(
			DataProxy.follow(GameDC.Trigger, 'UIGame', (_, show)=>{
				if(_==show) return void 0;
				if(show) this.enter().then(()=>this.initGuide());
				else this.leave();
			}, false),
		);

		if(GameDC.Trigger.PreLoadTiles == 'start'){
			this.preLoadTiles();
		}else{
			let reg = DataProxy.follow(GameDC.Trigger, 'PreLoadTiles', (_, status)=>{
				if(status==='start'){
					DataProxy.cancel(reg);
					this.preLoadTiles();
				}
			}, false);
		}
	}

	private async enter(duration?: number){
		duration = duration ?? 0.5;
		let np = new mtec.NudityPromise<'end'>();
		this.ui_container.node.active = true;

		Tween.stopAllByTarget(this.node);
		this.vecasit.vec.x = this.vecasit.over_right;
		this.node.setPosition(this.vecasit.vec);

		await Promise.all([this.inited_np.promise, this.prms_render]);

		AudioManager.playEffect(AudioManager.EAudio.EF_BOARD);

		this.vecasit.vec.x = this.vecasit.center.x;

		tween(this.node)
		.to(duration, {position: this.vecasit.vec}, {easing: 'cubicOut'})
		.call(()=>np.resolve('end'))
		.start();

		return np.promise;
	}

	private leave(duration?: number){
		duration = duration ?? 0.5;

		this.vecasit.vec.x = this.vecasit.over_right;

		Tween.stopAllByTarget(this.uitrans.node);
		tween(this.uitrans.node)
		.to(duration, {position: this.vecasit.vec}, {easing: 'cubicOut'})
		.start();
	}

	private touch = {
		location: v2(),
		select: v3(),
		positon: v3(),
	}
	private touchEnd(event: EventTouch){
		if(GameDC.Status.shuffling) return void 0;
		event.getUILocation(this.touch.location);

		this.selectTile(this.touch.location);
	}

	/**
	 * 根据给定的世界坐标选取砖块
	 * @param x
	 * @param y
	 */
	private selectTile(x: Vec3|Vec2|number, y?: number){
		if(x instanceof Vec3) this.touch.select.set(x);
		else if(x instanceof Vec2) this.touch.select.set(x.x, x.y);
		else this.touch.select.set(x, y);

		this.ui_container.convertToNodeSpaceAR(this.touch.select, this.touch.positon);

		let select_item: typeof this.tile_records[number]['tiles'][number];
		let tier: number;
		let distance = v3();
		for(let {tier: t, tiles} of this.tile_records){
			select_item = tiles.find(el=>{
				if(el.tile.Light){
					Vec3.subtract(distance, el.tile.node.position, this.touch.positon);
					return [distance.x/this.tile_size.width, distance.y/this.tile_size.height].every(v=>Math.abs(v)<0.5);
				}
			});
			if(select_item){
				tier = t;
				break;
			}
		}

		if(select_item) GameBoard.select_call_list.forEach(call=>call({tier, ...select_item}));
	}

	/** 预加载砖块 */
	private async preLoadTiles(){
		let round_layout = await GameAsset.loadRoundLayout('level_'+GameDC.Cache.round_curr);
		let count = round_layout.reduce((acc, cur)=>acc+cur.coor.length, 0);
		let cut_length = Math.max(5, Math.floor(count / 5000 * 15));
		for(let progress = 0; progress < count; progress += cut_length){
			this.updateCallBak((p, t, c)=>{
				let done = p+c >= t;
				while(c>0){
					GameBoard.tile_pool.put(instantiate(this.tile_prefab));
					c--;
				}

				if(done){
					GameDC.Trigger.PreLoadTiles = 'end';
				}
			}, progress, count, cut_length);
		}
	}

	private cleanBoard(){

		this.tile_records = [];

		this.ui_container.node.children.filter(n=>n.name.startsWith('tier:')).forEach((node, index)=>{
			node.getComponentsInChildren(Tile).forEach(t=>GameBoard.recycleTile(t));
			node.name = 'tier:'+(index+1);
		});

		this.ui_container.node.children.filter(n=>!n.name.startsWith('tier:')).forEach(n=>n.destroy());

		Reflect.deleteProperty(this, 'loop');
	}

	private refreshLight(tier_idx: number){
		let index = this.tile_records.findIndex(el=>el.tier==tier_idx);
		let distance = v2();
		for(let idx = index-1; idx >= 0; idx--){
			let tier = this.tile_records[idx];
			for(let item of tier.tiles){
				tile_light: for(let i = idx+1; i < this.tile_records.length; i++){
					for(let el of this.tile_records[i].tiles){
						Vec2.subtract(distance, el.coor, item.coor);
						if([distance.x, distance.y].every(v=>Math.abs(v)<1)){
							item.tile.setLight(false);
							break tile_light;
						}
					}
					item.tile.setLight(true);
				}
			}
		}
	}

	/**
	 * 渲染关卡数据
	 * @param data 关卡数据
	 */
	public renderBoard(data: Array<{tier: number, element: Array<{id: string, coor: Array<Vec2>}>}>){
		this.cleanBoard();

		this.tile_count = 0;
		Reflect.deleteProperty(this, 'loop_info');

		let __distance__ = v2();
		let list = data.map((tier, index, all)=>{
			let element: Array<{id: string, coor: Vec2, light: boolean}> = [];

			tier.element.forEach(el=>el.coor.forEach(c=>{
				let item = {id: el.id, coor: c, light: true};
				element.push(item);

				for(let t = index+1, end = all.length; t < end; t++){
					for(let _el_ of all[t].element){
						for(let _c_ of _el_.coor){
							Vec2.subtract(__distance__, _c_, c);
							if([__distance__.x, __distance__.y].every(v=>Math.abs(v)<1)){
								item.light = false;
								return void 0;
							}else continue;
						}
					}
				}
			}));

			element.sort((a,b)=>b.coor.y-a.coor.y);
			this.tile_count += element.length;

			return {tier: tier.tier, element}
		});
		__distance__ = null;


		return this.prms_render = this.updateFor(list, tier=>{
			let record = this.createTier(tier);
			this.tile_records.push(record);
		}).then(()=>{
			this.tile_records.sort((a, b)=>a.tier-b.tier);
			return 'end';
		});
	}

	private renderBoardTest(data: Awaited<ReturnType<typeof GameAsset.loadRoundData>>){
		this.cleanBoard();

		this.tile_count = 0;
		Reflect.deleteProperty(this, 'loop_info');
		type Tile = {id: string, coor: Vec2, light: boolean};
		type Tier = {tier: number, element: Tile[]}

		let calculate_v2 = v2();
		let light_tiles: Tier[] = [];
		let dark_tiles: Tier[] = [];

		data.forEach((tier, tidx, all)=>{
			let light_ls = light_tiles.find(el=>el.tier==tier.tier);
			let dark_ls = dark_tiles.find(el=>el.tier==tier.tier);
			if(!light_ls){
				light_ls = {tier: tier.tier, element: []};
				light_tiles.push(light_ls);
			}
			if(!dark_ls){
				dark_ls = {tier: tier.tier, element: []};
				dark_tiles.push(dark_ls);
			}

			tier.element.forEach(tile=>{
				let item: Tile = {id: tile.id, coor: null, light: true};
			});
		});
	}

	private createTier(info: {tier: number, element: {id: string, coor: Vec2, light: boolean}[]}){
		let tier_name = 'tier:'+info.tier;
		let record: typeof this.tile_records[number] = {tier: info.tier, node: null, tiles: []};
		record.node = this.ui_container.node.getChildByName(tier_name);
		if(!record.node){
			record.node = new Node(tier_name);
			record.node.setParent(this.ui_container.node);
			let ui = record.node.addComponent(UITransform);
			ui.setContentSize(this.ui_container.contentSize);
			ui.setAnchorPoint(0.5, 0.5);
			Vec3.zero(ui.node.position);
			record.node.setSiblingIndex(info.tier > 0 ? this.ui_container.node.children.length : 0);
		}

		info.element.forEach(el=>{
			let tile = GameBoard.getTile();
			record.tiles.push({coor: el.coor, tile});

			tile.node.setParent(record.node);

			tile.init(el.id, this.tile_size);
			tile.setLight(el.light);

			tile.node.setPosition(
				el.coor.x * this.tile_size.width,
				el.coor.y * this.tile_size.height,
				tile.node.position.z
			);
		});

		return record;
	}

	public appendingTile(tier_idx: number, coor: Vec2, tile: Tile){
		let tier = this.tile_records.find(el=>el.tier==tier_idx);
		if(!tier) return void 0;
		tile.setSize(this.tile_size);
		let target_position = this.getPosition(coor);
		target_position.z = tile.node.position.z;

		this.refreshLight(tier_idx);

		return this.uitrans.convertToWorldSpaceAR(target_position);
	}

	public appendedTile(tier_idx: number, coor: Vec2, tile: Tile){
		let tier = this.tile_records.find(el=>el.tier==tier_idx);
		if(!tier) return void 0;

		let target_position = this.getPosition(coor);

		tier.tiles.push({coor, tile});
		tier.tiles.sort((a, b)=>b.coor.y-a.coor.y);
		tile.node.removeFromParent();
		tile.node.setParent(tier.node);
		tile.node.setPosition(target_position);
		tile.node.setSiblingIndex(tier.tiles.findIndex(el=>el.tile==tile));
	}

	public removeTile(tier_idx: number, tile: Tile){
		this.testLoop(tile);
		let tier = this.tile_records.find(el=>el.tier==tier_idx);
		if(!tier) return void 0;

		let info = mtec.array.remove(tier.tiles, el=>el.tile==tile);
		if(info) this.refreshLight(tier_idx);
	}

	public shuffle(){
		GameDC.Status.shuffling = true;
		let radius = v2(...[this.uitrans.width, this.uitrans.height].map(v=>v/6));

		let tween_list: Tween<Node>[] = [];
		let duration = 3;
		let angle = 360 * 2;
		this.tile_records.forEach(tier=>{
			let tier_rotation_tween = tween(tier.node).by(duration, {angle}, {easing: 'backInOut'}).set({angle: 0});
			tween_list.push(tier_rotation_tween);

			let [item_list, coor_list] = tier.tiles.reduce((acc, cur)=>{
				acc[0].push(cur);
				acc[1].push(cur.coor);
				return acc;
			}, [[], []] as [{coor: Vec2, tile: Tile}[], Vec2[]]);

			item_list.sort(()=>Math.random()-0.5);
			coor_list.sort(()=>Math.random()-0.5);

			item_list.forEach((item, index)=>{
				item.tile.setLight(true);
				item.coor = coor_list[index];

				let start = tween(item.tile.node).to(0.25 * duration, {position: v3(mtec.number.random(-radius.x, radius.x), mtec.number.random(-radius.y, radius.y))});
				let rotation = tween(item.tile.node).by(duration, {angle: -angle}, {easing: 'backInOut'}).set({angle: 0});
				let end = tween(item.tile.node).delay(0.75 * duration).to(0.25 * duration, {position: this.getPosition(item.coor)});

				tween_list.push(tween(item.tile.node).parallel(start, rotation, end));
			});
		});

		tween_list.forEach(tw=>tw.start());
		mtec.delay(duration + 0.2, ()=>{
			GameDC.Status.shuffling = false;
			this.tile_records.forEach(tier=>{
				[...tier.node.children].sort((a, b)=>a.position.y-b.position.y).forEach((node, index)=>{
					node.setSiblingIndex(index);
				});
			});
			this.refreshLight(this.tile_records.lastElement.tier);
		});
	}

	private loop: {
		layout: Awaited<ReturnType<typeof GameAsset.loadRoundLayout>>;
		id_list: string[];
		count: number;
		check_list: Tile[];
	};
	public setLoop(layout: Awaited<ReturnType<typeof GameAsset.loadRoundLayout>>){
		let id_list = mtec.array.randomeElement(mtec.array.create(19, i=>(i+1).toString().padStart(2, '0')), 6);
		this.loop = { layout, id_list, count: 0, check_list: [], };
		this.refreshLoop();
	}

	private addLoop(){
		DataManager.userPoint('ReachButtonLine');
		let bucket = mtec.array.randomeElement(this.loop.id_list, 4).map(id=>{ return {id, count: 2} });

		mtec.cc.frameWhile(this.loop.layout, async el=>{
			let item: Parameters<typeof this.createTier>[0] = {tier: -this.loop.count-el.tier, element: []};
			el.coor.forEach(coor=>{
				let search = mtec.array.random(bucket);
				while(search.count <= 0){
					mtec.array.remove(bucket, search);
					search = mtec.array.random(bucket);
				}
				search.count--;
				item.element.push({id: search.id, coor, light: false});
			});
			this.loop.count++;
			let record = this.createTier(item);
			this.tile_count += record.tiles.length;
			this.tile_records.unshift(record);
		}).then(()=>this.refreshLoop());
	}

	private refreshLoop(){
		if(!this.loop || this.tileCount==0) return void 0;

		let temp: Map<Vec2, {tier: number, tile: Tile}[]> = new Map(this.loop.layout.reduce((list, t)=>{
			t.coor.forEach(coor=>{
				let str = [coor.x, coor.y].join('::');
				if(list.includes(str)) return void 0;
				list.push(str);
			});
			return list;
		}, [] as string[]).map(el=>{
			let [x, y] = el.split('::').map(_=>Number(_));
			return [v2(x, y), []]
		}));

		this.tile_records.forEach(t=>{
			t.tiles.forEach(tile=>{
				temp.forEach((list, coor)=>{
					if(tile.coor.x==coor.x && tile.coor.y==coor.y) list.push({tier: t.tier, tile: tile.tile});
				});
			});
		});

		mtec.array.clear(this.loop.check_list);

		for(let [coor, list] of temp){
			if(list.length<2){
				this.addLoop();
				break;
			}
			this.loop.check_list.push(list.sort((a, b)=>a.tier-b.tier)[1].tile);
		}
	}

	private testLoop(tile: Tile){
		if(!this.loop) return void 0;
		if(this.loop.check_list.includes(tile)) this.addLoop();
	}

	private getPosition(coor: Vec2, out?: Vec3){
		out = out ?? v3();
		out.set(
			coor.x * this.tile_size.width,
			coor.y * this.tile_size.height,
			out.z
		);
		return out;
	}

	protected recordToken(record: {tier: number, coor: Vec2, tile: Tile, token?: string}){
		if(record.token) return record.token;
		else record.token = [
			['id', record.tile.ID],
			['tier', record.tier],
			['x', record.coor.x],
			['y', record.coor.y]
		].map(el=>el.join(':')).join('; ');

		return record.token;
	}

	private async initGuide(){
		if([1, 2, 3].every(id=>GameDC.checkGuideID(id))){
			return void 0;
		}
		GameDC.CleanGuideID(1, 2, 3, 4);

		await DataManager.GuideInited;

		let m = new Map<string, {tier: number, coor: Vec2, tile: Tile}[]>();

		this.tile_records.forEach(t=>{
			t.tiles.forEach(b=>{
				if(!b.tile.Light) return void 0;

				let ls = m.get(b.tile.ID);
				if(!ls){
					ls = [];
					m.set(b.tile.ID, ls);
				}

				ls.push({tier: t.tier, ...b});
			});
		});

		let list = Array.from(m)
		.map(e=>e[1]).filter(e=>e.length>=3)
		.sort((a, b)=>Math.max(...a.map(e=>e.coor.y))-Math.max(...b.map(e=>e.coor.y)))
		.lastElement.sort((a, b)=>a.coor.x-b.coor.x);

		let ui_drawer = find('drawer-container/background', this.node.parent).getComponent(UITransform);

		Guide.Create(1,
			'Click on a brick to move it to the bottom slot',
			ui_drawer,
			list[0].tile.node.getComponent(UITransform)
		);
		Guide.Create(2,
			'Click on a brick to move it to the bottom slot',
			ui_drawer,
			list[1].tile.node.getComponent(UITransform)
		);
		Guide.Create(3,
			'Keep clicking! Three identical bricks will be eliminated',
			ui_drawer,
			list[2].tile.node.getComponent(UITransform)
		);

		Guide.Show(1);

		Guide.Wait(1, false).then(()=>{
			GameBoard.select_call_list.forEach(call=>call(list[0]));
			Guide.Show(2);
		});
		Guide.Wait(2, false).then(()=>{
			GameBoard.select_call_list.forEach(call=>call(list[1]));
			Guide.Show(3);
		});
		Guide.Wait(3, true).then(()=>{
			GameBoard.select_call_list.forEach(call=>call(list[2]));
		});
	}
}

